Skip to content

Conversation

@ochafik
Copy link
Contributor

@ochafik ochafik commented Jan 11, 2026

This PR tightens CSP handling in basic-host (which was setting very loose permissions so far).

Changes

  • CSP via HTTP headers instead of meta tags - Server sets CSP headers based on query params, guest content cannot modify them
  • Added frameDomains, baseUriDomains (borrowed from feat: enhance sandbox capability negotiation #158, cc/ @idosal @domfarolino) - Extend CSP configuration options
  • Added worker-src directive - Needed for WebGL apps (CesiumJS, Three.js) that use Web Workers for tile decoding, terrain processing, etc.
  • Use document.write() instead of srcdoc - Fixes WebGL rendering issues (srcdoc creates opaque origin that breaks canvas updates)

(last two points fix the upcoming Map App example #235 )

Implementation

  1. Host passes CSP config as query param to sandbox.html
  2. serve.ts parses query param and sets CSP via HTTP response headers
  3. sandbox.ts no longer injects meta tags (relies on headers)

Note: basic-host is still not to be considered as a hardened production host.

🤖 Generated with Claude Code

…Domains, permissions

Security improvements:
- CSP is now set via HTTP headers in serve.ts instead of meta tags
  (meta tag CSP can be tampered with by same-origin content)
- CSP passed as query param to sandbox.html for header-based enforcement

New CSP/permissions features (borrowed from PR #158):
- frameDomains: control frame-src directive for nested iframes
- baseUriDomains: control base-uri directive
- permissions: camera, microphone, geolocation via iframe allow attribute

WebGL fix:
- Use document.write() instead of srcdoc for inner iframe content
  (srcdoc creates opaque origin that breaks WebGL canvas updates)
- Add worker-src directive with blob: support (critical for WebGL apps
  like CesiumJS/Three.js that use workers for tile decoding, terrain
  processing, image processing)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 11, 2026

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/ext-apps@234

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-basic-react@234

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-basic-vanillajs@234

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-budget-allocator@234

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-cohort-heatmap@234

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-customer-segmentation@234

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-scenario-modeler@234

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-system-monitor@234

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-threejs@234

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/server-wiki-explorer@234

commit: adf7088

Keep this PR focused on CSP security fixes only.
Permissions (camera, microphone, geolocation) will be
handled in #158.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@ochafik ochafik changed the title fix: Harden CSP handling in basic-host (HTTP headers + worker-src) fix: proper CSP handling in basic-host sandboxing (HTTP headers, worker-src, document.write) Jan 11, 2026
@ochafik ochafik marked this pull request as ready for review January 11, 2026 13:41
@ochafik ochafik requested a review from idosal January 11, 2026 13:41
ochafik added a commit to ochafik/mcp-ui that referenced this pull request Jan 11, 2026
Add support for passing CSP configuration via URL query parameter (?csp=<json>)
to the sandbox proxy. This enables proxy servers to set Content-Security-Policy
via HTTP headers (tamper-proof) rather than relying on meta tags or postMessage.

Changes:
- AppFrame.tsx: Build sandbox URL with CSP query param before loading iframe
- SandboxConfig.csp: Updated docs explaining query-param + postMessage fallback
- using-a-proxy.md: Added CSP Query Parameter section with server-side example
- Updated architecture diagram to show CSP flow through server

The CSP is still sent via postMessage as a fallback for proxies that don't
support the query parameter approach.

See: modelcontextprotocol/ext-apps#234
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 2
Claude-Permission-Prompts: 0
Claude-Escapes: 0
// Images: same-origin + data/blob URIs + specified domains
`img-src 'self' data: blob: ${resourceDomains}`.trim(),
// Fonts: same-origin + data/blob URIs + specified domains
`font-src 'self' data: blob: ${resourceDomains}`.trim(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude do I understand correctly that we have a risk of csp injection here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a fix, ptal @antonpk1

Validate CSP domain entries to reject characters that could:
- Break out of CSP directives (semicolons, newlines)
- Inject CSP keywords like 'unsafe-eval' (quotes)
- Inject multiple sources in one entry (spaces)

This prevents injection attacks where malicious domains could
override the security policy.
@ochafik ochafik requested a review from antonpk1 January 11, 2026 14:30
@ochafik ochafik merged commit 8b09f42 into main Jan 11, 2026
19 checks passed
modifiedHtml = cspMetaTag + modifiedHtml;
}
// Use document.write instead of srcdoc for WebGL compatibility.
// srcdoc creates an opaque origin which prevents WebGL canvas updates

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this bit. srcdoc iframes are not all opaque-origined. For example, navigate to https://example.com and run this in DevTools:

const iframe = document.createElement('iframe');
iframe.srcdoc = `<p id=log></p><script>log.textContent = self.origin</script>`;
document.body.append(iframe);

By default, srcdoc iframes inherit their navigation initiator's origin (i.e., the origin of the document that set the srcdoc attribute), unless their sandbox attribute overrides this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops sorry good catch, will send update

Real reason: was to work around browser canvas tainting checks that seemed to treat about:srcdoc URLs as tainted, even when the iframe is same-origin with its parent. Will confirm and update the comment. in a followup

severity: "high",
},
{
target: "Element.prototype",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What dictates what's in this list? Is there documentation somewhere? APIs like setHTMLUnsafe() come to mind (maybe it's fine since it's already unsafe), but I was just curious how these were chosen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh this wasn't meant to be submitted, sorry :-(

@ochafik ochafik deleted the ochafik/fix-csp branch January 12, 2026 21:29
@ochafik ochafik restored the ochafik/fix-csp branch January 12, 2026 21:29
ochafik added a commit that referenced this pull request Jan 12, 2026
Follow-up to PR #234. This file was accidentally included.
ochafik added a commit that referenced this pull request Jan 12, 2026
Update comment to accurately explain the canvas tainting issue rather than
incorrectly claiming srcdoc creates an opaque origin.

Follow-up to PR #234 per domfarolino's review feedback.
ochafik added a commit that referenced this pull request Jan 12, 2026
Follow-up to PR #234 per domfarolino's review feedback.
ochafik added a commit that referenced this pull request Jan 12, 2026
…nt.write to equiv. srcdoc) (#248)

* chore: remove tamper-detection.ts (not meant to be submitted)

Follow-up to PR #234. This file was accidentally included.

* revert to srcdoc

Follow-up to PR #234 per domfarolino's review feedback.

* regen

* regen
@ochafik ochafik mentioned this pull request Jan 12, 2026
@ochafik ochafik deleted the ochafik/fix-csp branch January 13, 2026 15:29
idosal added a commit to MCP-UI-Org/mcp-ui that referenced this pull request Jan 21, 2026
* UITemplatedToolCallRendererProps for MCP Apps

* Upgrade MCP SDK to 1.22.0 (many (Embedded)Resource type fixes)

* Update adapter.ts

* Update UIResourceRendererWC.test.tsx

* add missing client dep (vite-tsconfig-paths)

* update to latest ext-apps example renderer

* Sync with latest ext-apps, fix PR review comments

- Fix Zod v4 compatibility: .value → .values[0] (guru3s)
- Fix index.ts export: replace broken UITemplatedToolCallRenderer with AppRenderer
- Upgrade @modelcontextprotocol/sdk to ^1.23.0 to match ext-apps
- Use RESOURCE_URI_META_KEY from ext-apps instead of local constant
- Fix logging message type handling

Note: ext-apps dependency temporarily uses file: reference due to
git install issues with esbuild prepare script.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* Add AppFrame component, refactor AppRenderer, use ext-apps v0.1.0

- Add AppFrame: low-level component for rendering pre-fetched HTML
  - Takes html directly, optionally with pre-configured AppBridge
  - Supports simple callbacks (onOpenLink, onMessage, onSizeChange)
  - Forwards CSP metadata to sandbox proxy

- Refactor AppRenderer to use AppFrame internally
  - Add sandbox prop with SandboxConfig type (replaces sandboxProxyUrl)
  - Add optional html prop to skip resource fetching
  - Deprecate sandboxProxyUrl (still works with warning)
  - Use proper param types in callbacks (McpUiMessageRequest, etc.)

- Update to @modelcontextprotocol/ext-apps ^0.1.0
  - Use RESOURCE_MIME_TYPE from ext-apps
  - Import from /app-bridge subpath

- Export AppFrame, AppFrameProps, SandboxConfig from index.ts

Addresses PR comments:
- @idosal: Extract bare-bones component, sandbox prop with object
- @infoxicator, @chelojimenez, @liady: Optional HTML pass-through mode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* feat: use ext-apps branch with setter-based MCP forwarding handlers

- Update @modelcontextprotocol/ext-apps to ochafik/app-bridge-setters branch
- This enables optional MCP client in AppBridge constructor
- Adds oncalltool, onlistresources, onreadresource, etc. setters for custom handlers
- Adds sendToolListChanged, sendResourceListChanged, sendPromptListChanged methods

* feat(client): expose MCP request handlers and AppBridge ref

Option 1 - Add request handler props to AppRendererProps:
- oncalltool: handle tools/call requests
- onlistresources: handle resources/list requests
- onlistresourcetemplates: handle resources/templates/list requests
- onreadresource: handle resources/read requests
- onlistprompts: handle prompts/list requests

Option 2 - Expose AppBridge via ref (AppRendererHandle):
- appBridge: direct access to AppBridge instance
- sendToolListChanged(): notify guest of tool list changes
- sendResourceListChanged(): notify guest of resource list changes
- sendPromptListChanged(): notify guest of prompt list changes

Option 3 - Re-export from index.ts:
- AppBridge: for creating custom bridges
- PostMessageTransport: for custom transport setups

Also:
- Make client prop nullable (required html when client is null)
- Export RequestHandlerExtra type for custom handler signatures

* refactor(client): require AppBridge in AppFrame, cleaner AppRenderer API

Breaking changes:
- AppFrame.appBridge is now required (was optional)
- Removed postMessage fallback from AppFrame

AppRenderer changes:
- Always creates AppBridge internally
- client prop can be null (requires html prop when null)
- Exposes ref handle with send methods (sendToolListChanged, etc.)
- Removed appBridge from ref handle (use AppFrame directly for full control)

New MCP request handler props on AppRenderer:
- oncalltool
- onlistresources
- onlistresourcetemplates
- onreadresource
- onlistprompts

New send methods on AppRendererHandle:
- sendToolListChanged
- sendResourceListChanged
- sendPromptListChanged
- sendToolInput
- sendToolInputPartial
- sendToolResult
- sendToolCancelled
- sendHostContextChange

Re-exports from @mcp-ui/client:
- AppBridge
- PostMessageTransport

* refactor(client): require AppBridge in AppFrame, cleaner AppRenderer API

Breaking changes:
- AppFrame.appBridge is now required (was optional)
- Removed postMessage fallback from AppFrame

AppRenderer changes:
- Always creates AppBridge internally
- client prop can be null (requires html prop when null)
- Exposes ref handle with send methods (sendToolListChanged, etc.)
- Removed appBridge from ref handle (use AppFrame directly for full control)

New MCP request handler props on AppRenderer:
- oncalltool
- onlistresources
- onlistresourcetemplates
- onreadresource
- onlistprompts

New send methods on AppRendererHandle:
- sendToolListChanged
- sendResourceListChanged
- sendPromptListChanged
- sendToolInput
- sendToolInputPartial
- sendToolResult
- sendToolCancelled
- sendHostContextChange

Re-exports from @mcp-ui/client:
- AppBridge
- PostMessageTransport

* chore: prettier formatting + re-export McpUiHostContext type

* refactor(client): cleaner API with props instead of ref methods

API changes:

AppRendererHandle (ref):
- Remove: sendToolInput, sendToolResult, sendToolInputPartial, sendToolCancelled
- Add: sendResourceTeardown (for cleanup before unmounting)
- Keep: sendToolListChanged, sendResourceListChanged, sendPromptListChanged

AppRendererProps:
- Add: toolInputPartial (for streaming partial input)
- Add: toolCancelled (boolean flag for cancellation)
- Deprecate: onUIAction (use onopenlink, onmessage, onloggingmessage instead)

AppFrameProps callback naming (camelCase):
- onSizeChanged (was onSizeChange)
- onLoggingMessage
- onInitialized

* refactor(client): use camelCase for all callback props

AppRendererProps:
- onOpenLink (was onopenlink)
- onMessage (was onmessage)
- onLoggingMessage (was onloggingmessage)
- onSizeChanged (was onsizechange)
- onError (was onerror)
- onCallTool (was oncalltool)
- onListResources (was onlistresources)
- onListResourceTemplates (was onlistresourcetemplates)
- onReadResource (was onreadresource)
- onListPrompts (was onlistprompts)

AppFrameProps:
- onSizeChanged
- onLoggingMessage
- onInitialized
- onError (was onerror)

* test(client): add comprehensive tests for AppRenderer

New test coverage:
- hostContext prop (setHostContext calls)
- toolInputPartial prop (sendToolInputPartial calls)
- toolCancelled prop (sendToolCancelled calls)
- ref methods: sendToolListChanged, sendResourceListChanged, sendPromptListChanged, sendResourceTeardown
- MCP request handler props: onCallTool, onListResources, onListResourceTemplates, onReadResource, onListPrompts
- callback props forwarding: onSizeChanged, onError
- null client behavior (with/without html prop)

* chore: update ext-apps to latest, fix test types

- Update @modelcontextprotocol/ext-apps to 4653156 (latest on ochafik/app-bridge-setters)
- Fix hostContext test to use proper McpUiTheme literal types ('dark' | 'light')
- Fix toolInputPartial test to match McpUiToolInputPartialNotification params shape

* chore: add ESLint flat config for v9 compatibility

- Create eslint.config.mjs for ESLint v9 flat config format
- Disable @typescript-eslint/no-empty-object-type (pre-existing issues)
- Remove unused imports from AppRenderer.tsx
- Clean up AppRenderer.test.tsx (remove unused helper)

* chore: switch ext-apps dependency to main branch

The ochafik/app-bridge-setters branch has been merged to main.

* chore(client): regenerate iframe-bundle with updated dependencies

- @remote-dom/core 1.8.1 → 1.10.1
- @quilted/threads 3.1.3 → 3.3.1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* feat(client): make Client optional with onReadResource alternative

Addresses PR feedback:
- Changed `client: Client | null` to `client?: Client` for cleaner API
- Added support for using `onReadResource` + `toolResourceUri` to fetch
  HTML without requiring the full MCP Client instance
- This enables decoupled architectures where the MCP client lives in a
  different context (e.g., server-side)

Usage without client:
```tsx
<AppRenderer
  toolName="my-tool"
  toolResourceUri="ui://my-server/my-tool"
  onReadResource={async ({ uri }) => myProxy.readResource({ uri })}
  onCallTool={async (params) => myProxy.callTool(params)}
/>
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* chore: update ext-apps to ^0.2.0 and MCP SDK to ^1.24.0

Breaking changes in ext-apps v0.2.0:
- sendResourceTeardown() renamed to teardownResource() (deprecated alias exists)
- MCP SDK moved to peer dependency, requiring ^1.24.0
- Method renaming: sendOpenLink() → openLink() (not used in mcp-ui)

Updates:
- Update @modelcontextprotocol/ext-apps from github#main/^0.0.7 to ^0.2.0
- Update @modelcontextprotocol/sdk from ^1.22.0/^1.23.0 to ^1.24.0
- Rename AppRendererHandle.sendResourceTeardown to teardownResource
- Update tests to use new method name

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* docs: add AppRenderer/AppFrame docs, fix sandbox promise rejection

- Add "Host-Side Rendering" section to mcp-apps.md documenting:
  - AppRenderer component usage and props
  - Using without an MCP client (custom handlers or pre-fetched HTML)
  - AppFrame low-level component
  - Sandbox proxy requirements

- Fix promise rejection in setupSandboxProxyIframe:
  - Add 10s timeout for sandbox ready message
  - Add error listener for iframe load failures
  - Proper cleanup of event listeners

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* fix build

* remove mcp-ui/shared dependency

It wasn't used and caused install to fail

* remove deprecated AppRenderer API

* fix lifecycle

* use experimental ext-apps for types

* pnpm lock

* Update sdks/typescript/client/src/components/AppFrame.tsx

Co-authored-by: Ruben Casas <[email protected]>

* fix: remove onLoggingMessage from AppFrame

AppFrame is a low-level component that takes a pre-configured appBridge.
The caller owns the bridge and should configure handlers directly.
This prevents AppFrame from overwriting handlers set by AppRenderer.

onLoggingMessage remains available in AppRenderer.

* Update to latest sdk changes: registerApp*, _meta.ui, getToolUiResourceUri

* chore: upgrade @modelcontextprotocol/ext-apps to 0.3.1

Breaking changes addressed:
- Renamed viewport.maxHeight to containerDimensions.maxHeight
- Updated type handling for the new union type structure
- Fixed vitest config to exclude .pnpm-store temp files

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0

* refactor: use SANDBOX_PROXY_READY_METHOD from ext-apps 0.3.1

Now that @modelcontextprotocol/ext-apps exports method constants,
import SANDBOX_PROXY_READY_METHOD directly instead of defining it locally.

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0

* feat(client): add UI extension capabilities for client capability negotiation

Add typed helpers for declaring UI extension support when connecting to MCP
servers, following the SEP-1724 extensions field pattern.

New exports from @mcp-ui/client:
- ClientCapabilitiesWithExtensions: Extended type with extensions field
- UI_EXTENSION_NAME: Extension identifier 'io.modelcontextprotocol/ui'
- UI_EXTENSION_CONFIG: Config with mimeTypes array
- UI_EXTENSION_CAPABILITIES: Ready-to-use capabilities object

This enables consumers to declare UI extension support when creating MCP clients:

  const client = new Client(
    { name: 'my-app', version: '1.0.0' },
    { capabilities: { extensions: UI_EXTENSION_CAPABILITIES } }
  );

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 1
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# Plan: Add Client Capabilities for UI Extension Support

## Context

The mcp-ui client SDK helps consumers render MCP UI resources, but it doesn't currently help them **declare UI extension support** when connecting to MCP servers. This is important because:

1. Servers may filter/adjust responses based on client capabilities
2. SEP-1724 proposes an `extensions` field for capability negotiation
3. Consumers shouldn't have to figure out the right structure themselves

## Design Analysis

### Current State
- MCP SDK's `ClientCapabilities` has an `experimental` field (open record)
- SEP-1724 proposes a new `extensions` field (not yet adopted)
- ext-apps exports `RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"`
- The mcp-ui client SDK takes an MCP `Client` as input but doesn't help configure it

### Design Decision

**Approach**: Export typed helpers from mcp-ui client SDK using the `extensions` field pattern proposed in SEP-1724.

```typescript
// Usage by consumer:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import {
  type ClientCapabilitiesWithExtensions,
  UI_EXTENSION_CAPABILITIES,
} from '@mcp-ui/client';

const capabilities: ClientCapabilitiesWithExtensions = {
  // Standard MCP capabilities
  roots: { listChanged: true },
  // UI extension (SEP-1724 pattern)
  extensions: UI_EXTENSION_CAPABILITIES,
};

const client = new Client(
  { name: 'my-app', version: '1.0.0' },
  { capabilities }
);
```

### Why This Approach

1. **Forward-compatible**: Follows SEP-1724 pattern, ready for adoption
2. **Type-safe**: Custom type extension makes intent explicit
3. **Clear semantics**: `extensions` field is specifically for extensions, not `experimental`
4. **Documented pattern**: Links to SEP-1724 for context

## Implementation Plan

### 1. Add capabilities module to client SDK

**File**: `sdks/typescript/client/src/capabilities.ts` (new file)

```typescript
import type { ClientCapabilities } from '@modelcontextprotocol/sdk/types.js';
import { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/app-bridge';

// Custom type ahead of the Extensions SEP making it to MCP
// modelcontextprotocol/modelcontextprotocol#1724
export interface ClientCapabilitiesWithExtensions extends ClientCapabilities {
  extensions?: {
    [extensionName: string]: unknown;
  };
}

/**
 * Extension identifier for MCP UI support.
 * Follows the pattern from SEP-1724: {vendor-prefix}/{extension-name}
 */
export const UI_EXTENSION_NAME = 'io.modelcontextprotocol/ui' as const;

/**
 * UI extension capability configuration.
 * Declares support for rendering UI resources.
 */
export const UI_EXTENSION_CONFIG = {
  mimeTypes: [RESOURCE_MIME_TYPE],
} as const;

/**
 * UI extension capabilities object to use in the `extensions` field.
 *
 * @example
 * ```typescript
 * import { Client } from '@modelcontextprotocol/sdk/client/index.js';
 * import {
 *   type ClientCapabilitiesWithExtensions,
 *   UI_EXTENSION_CAPABILITIES,
 * } from '@mcp-ui/client';
 *
 * const capabilities: ClientCapabilitiesWithExtensions = {
 *   extensions: UI_EXTENSION_CAPABILITIES,
 * };
 *
 * const client = new Client(
 *   { name: 'my-app', version: '1.0.0' },
 *   { capabilities }
 * );
 * ```
 */
export const UI_EXTENSION_CAPABILITIES = {
  [UI_EXTENSION_NAME]: UI_EXTENSION_CONFIG,
} as const;
```

### 2. Export from index.ts

**File**: `sdks/typescript/client/src/index.ts`

Add exports:
```typescript
// Client capabilities for UI extension support (SEP-1724)
export {
  type ClientCapabilitiesWithExtensions,
  UI_EXTENSION_NAME,
  UI_EXTENSION_CONFIG,
  UI_EXTENSION_CAPABILITIES,
} from './capabilities';
```

### 3. Add unit tests

**File**: `sdks/typescript/client/src/__tests__/capabilities.test.ts`

```typescript
import { describe, it, expect } from 'vitest';
import {
  type ClientCapabilitiesWithExtensions,
  UI_EXTENSION_NAME,
  UI_EXTENSION_CONFIG,
  UI_EXTENSION_CAPABILITIES,
} from '../capabilities';
import { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/app-bridge';

describe('UI Extension Capabilities', () => {
  it('should have correct extension name', () => {
    expect(UI_EXTENSION_NAME).toBe('io.modelcontextprotocol/ui');
  });

  it('should include RESOURCE_MIME_TYPE in mimeTypes', () => {
    expect(UI_EXTENSION_CONFIG.mimeTypes).toContain(RESOURCE_MIME_TYPE);
  });

  it('should structure capabilities with extension name as key', () => {
    expect(UI_EXTENSION_CAPABILITIES[UI_EXTENSION_NAME]).toEqual(
      UI_EXTENSION_CONFIG
    );
  });

  it('should work with ClientCapabilitiesWithExtensions type', () => {
    const capabilities: ClientCapabilitiesWithExtensions = {
      roots: { listChanged: true },
      extensions: UI_EXTENSION_CAPABILITIES,
    };

    expect(capabilities.roots).toEqual({ listChanged: true });
    expect(capabilities.extensions?.[UI_EXTENSION_NAME]).toEqual(UI_EXTENSION_CONFIG);
  });
});
```

### 4. Update documentation

**File**: `docs/src/guide/mcp-apps.md`

Add section on client configuration:
```markdown
## Declaring UI Extension Support

When creating your MCP client, declare UI extension support using the provided type and capabilities:

\`\`\`typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import {
  type ClientCapabilitiesWithExtensions,
  UI_EXTENSION_CAPABILITIES,
} from '@mcp-ui/client';

const capabilities: ClientCapabilitiesWithExtensions = {
  // Standard capabilities
  roots: { listChanged: true },
  // UI extension support (SEP-1724 pattern)
  extensions: UI_EXTENSION_CAPABILITIES,
};

const client = new Client(
  { name: 'my-app', version: '1.0.0' },
  { capabilities }
);
\`\`\`

This tells MCP servers that your client can render UI resources with MIME type \`text/html;profile=mcp-app\`.

> **Note:** This uses the \`extensions\` field pattern from [SEP-1724](modelcontextprotocol/modelcontextprotocol#1724), which is not yet part of the official MCP protocol.
```

## Files to Modify

1. `sdks/typescript/client/src/capabilities.ts` - **NEW** - capability constants
2. `sdks/typescript/client/src/index.ts` - add export
3. `sdks/typescript/client/src/__tests__/capabilities.test.ts` - **NEW** - unit tests
4. `docs/src/guide/mcp-apps.md` - add documentation section

## Verification

1. Run tests: `cd sdks/typescript/client && pnpm test`
2. Build SDK: `pnpm build`
3. Verify exports: Check that `UI_EXTENSION_CAPABILITIES` is exported correctly
4. Integration check: The capabilities object should be spreadable into MCP Client options

## Future Considerations

When SEP-1724 is adopted into MCP SDK:
1. Remove `ClientCapabilitiesWithExtensions` type extension
2. Update imports to use the official SDK type
3. No changes needed to `UI_EXTENSION_CAPABILITIES` structure
</claude-plan>

* fix(server): wrap case block with braces to fix no-case-declarations lint error

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# Plan: Add Client Capabilities for UI Extension Support

## Context

The mcp-ui client SDK helps consumers render MCP UI resources, but it doesn't currently help them **declare UI extension support** when connecting to MCP servers. This is important because:

1. Servers may filter/adjust responses based on client capabilities
2. SEP-1724 proposes an `extensions` field for capability negotiation
3. Consumers shouldn't have to figure out the right structure themselves

## Design Analysis

### Current State
- MCP SDK's `ClientCapabilities` has an `experimental` field (open record)
- SEP-1724 proposes a new `extensions` field (not yet adopted)
- ext-apps exports `RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"`
- The mcp-ui client SDK takes an MCP `Client` as input but doesn't help configure it

### Design Decision

**Approach**: Export typed helpers from mcp-ui client SDK using the `extensions` field pattern proposed in SEP-1724.

```typescript
// Usage by consumer:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import {
  type ClientCapabilitiesWithExtensions,
  UI_EXTENSION_CAPABILITIES,
} from '@mcp-ui/client';

const capabilities: ClientCapabilitiesWithExtensions = {
  // Standard MCP capabilities
  roots: { listChanged: true },
  // UI extension (SEP-1724 pattern)
  extensions: UI_EXTENSION_CAPABILITIES,
};

const client = new Client(
  { name: 'my-app', version: '1.0.0' },
  { capabilities }
);
```

### Why This Approach

1. **Forward-compatible**: Follows SEP-1724 pattern, ready for adoption
2. **Type-safe**: Custom type extension makes intent explicit
3. **Clear semantics**: `extensions` field is specifically for extensions, not `experimental`
4. **Documented pattern**: Links to SEP-1724 for context

## Implementation Plan

### 1. Add capabilities module to client SDK

**File**: `sdks/typescript/client/src/capabilities.ts` (new file)

```typescript
import type { ClientCapabilities } from '@modelcontextprotocol/sdk/types.js';
import { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/app-bridge';

// Custom type ahead of the Extensions SEP making it to MCP
// modelcontextprotocol/modelcontextprotocol#1724
export interface ClientCapabilitiesWithExtensions extends ClientCapabilities {
  extensions?: {
    [extensionName: string]: unknown;
  };
}

/**
 * Extension identifier for MCP UI support.
 * Follows the pattern from SEP-1724: {vendor-prefix}/{extension-name}
 */
export const UI_EXTENSION_NAME = 'io.modelcontextprotocol/ui' as const;

/**
 * UI extension capability configuration.
 * Declares support for rendering UI resources.
 */
export const UI_EXTENSION_CONFIG = {
  mimeTypes: [RESOURCE_MIME_TYPE],
} as const;

/**
 * UI extension capabilities object to use in the `extensions` field.
 *
 * @example
 * ```typescript
 * import { Client } from '@modelcontextprotocol/sdk/client/index.js';
 * import {
 *   type ClientCapabilitiesWithExtensions,
 *   UI_EXTENSION_CAPABILITIES,
 * } from '@mcp-ui/client';
 *
 * const capabilities: ClientCapabilitiesWithExtensions = {
 *   extensions: UI_EXTENSION_CAPABILITIES,
 * };
 *
 * const client = new Client(
 *   { name: 'my-app', version: '1.0.0' },
 *   { capabilities }
 * );
 * ```
 */
export const UI_EXTENSION_CAPABILITIES = {
  [UI_EXTENSION_NAME]: UI_EXTENSION_CONFIG,
} as const;
```

### 2. Export from index.ts

**File**: `sdks/typescript/client/src/index.ts`

Add exports:
```typescript
// Client capabilities for UI extension support (SEP-1724)
export {
  type ClientCapabilitiesWithExtensions,
  UI_EXTENSION_NAME,
  UI_EXTENSION_CONFIG,
  UI_EXTENSION_CAPABILITIES,
} from './capabilities';
```

### 3. Add unit tests

**File**: `sdks/typescript/client/src/__tests__/capabilities.test.ts`

```typescript
import { describe, it, expect } from 'vitest';
import {
  type ClientCapabilitiesWithExtensions,
  UI_EXTENSION_NAME,
  UI_EXTENSION_CONFIG,
  UI_EXTENSION_CAPABILITIES,
} from '../capabilities';
import { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/app-bridge';

describe('UI Extension Capabilities', () => {
  it('should have correct extension name', () => {
    expect(UI_EXTENSION_NAME).toBe('io.modelcontextprotocol/ui');
  });

  it('should include RESOURCE_MIME_TYPE in mimeTypes', () => {
    expect(UI_EXTENSION_CONFIG.mimeTypes).toContain(RESOURCE_MIME_TYPE);
  });

  it('should structure capabilities with extension name as key', () => {
    expect(UI_EXTENSION_CAPABILITIES[UI_EXTENSION_NAME]).toEqual(
      UI_EXTENSION_CONFIG
    );
  });

  it('should work with ClientCapabilitiesWithExtensions type', () => {
    const capabilities: ClientCapabilitiesWithExtensions = {
      roots: { listChanged: true },
      extensions: UI_EXTENSION_CAPABILITIES,
    };

    expect(capabilities.roots).toEqual({ listChanged: true });
    expect(capabilities.extensions?.[UI_EXTENSION_NAME]).toEqual(UI_EXTENSION_CONFIG);
  });
});
```

### 4. Update documentation

**File**: `docs/src/guide/mcp-apps.md`

Add section on client configuration:
```markdown
## Declaring UI Extension Support

When creating your MCP client, declare UI extension support using the provided type and capabilities:

\`\`\`typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import {
  type ClientCapabilitiesWithExtensions,
  UI_EXTENSION_CAPABILITIES,
} from '@mcp-ui/client';

const capabilities: ClientCapabilitiesWithExtensions = {
  // Standard capabilities
  roots: { listChanged: true },
  // UI extension support (SEP-1724 pattern)
  extensions: UI_EXTENSION_CAPABILITIES,
};

const client = new Client(
  { name: 'my-app', version: '1.0.0' },
  { capabilities }
);
\`\`\`

This tells MCP servers that your client can render UI resources with MIME type \`text/html;profile=mcp-app\`.

> **Note:** This uses the \`extensions\` field pattern from [SEP-1724](modelcontextprotocol/modelcontextprotocol#1724), which is not yet part of the official MCP protocol.
```

## Files to Modify

1. `sdks/typescript/client/src/capabilities.ts` - **NEW** - capability constants
2. `sdks/typescript/client/src/index.ts` - add export
3. `sdks/typescript/client/src/__tests__/capabilities.test.ts` - **NEW** - unit tests
4. `docs/src/guide/mcp-apps.md` - add documentation section

## Verification

1. Run tests: `cd sdks/typescript/client && pnpm test`
2. Build SDK: `pnpm build`
3. Verify exports: Check that `UI_EXTENSION_CAPABILITIES` is exported correctly
4. Integration check: The capabilities object should be spreadable into MCP Client options

## Future Considerations

When SEP-1724 is adopted into MCP SDK:
1. Remove `ClientCapabilitiesWithExtensions` type extension
2. Update imports to use the official SDK type
3. No changes needed to `UI_EXTENSION_CAPABILITIES` structure
</claude-plan>

* feat(client): add CSP query parameter support for HTTP header-based CSP

Add support for passing CSP configuration via URL query parameter (?csp=<json>)
to the sandbox proxy. This enables proxy servers to set Content-Security-Policy
via HTTP headers (tamper-proof) rather than relying on meta tags or postMessage.

Changes:
- AppFrame.tsx: Build sandbox URL with CSP query param before loading iframe
- SandboxConfig.csp: Updated docs explaining query-param + postMessage fallback
- using-a-proxy.md: Added CSP Query Parameter section with server-side example
- Updated architecture diagram to show CSP flow through server

The CSP is still sent via postMessage as a fallback for proxies that don't
support the query parameter approach.

See: modelcontextprotocol/ext-apps#234
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 2
Claude-Permission-Prompts: 0
Claude-Escapes: 0

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Ido Salomon <[email protected]>
Co-authored-by: Ruben Casas <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants